package com.tools.commons.utils;

import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpTrace;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class HttpClientUtil {
    private static final Logger log = LoggerFactory.getLogger(HttpClientUtil.class);
    private URIBuilder uriBuilder;
    private Method method;
    private Map<String, String> params;
    private Map<String, String> headers;
    private Map<String, Object> forms;
    private String bodyStr;
    private RequestListener listener;
    private ContentType contentType;
    private static final PoolingHttpClientConnectionManager CONNECTION_MANAGER;

    private HttpClientUtil() {
        this.params = new HashMap<>();
        this.headers = new HashMap<>();
        this.forms = new HashMap<>();
    }

    private static HttpClient getClient() {
        return HttpClients
                .custom()
                .setConnectionManager(CONNECTION_MANAGER)
                .build();
    }

    static {
        try {
            CONNECTION_MANAGER = new PoolingHttpClientConnectionManager();
            // Increase max total connection to 200
            CONNECTION_MANAGER.setMaxTotal(200);
            // Increase default max connection per route to 20
            CONNECTION_MANAGER.setDefaultMaxPerRoute(20);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    private String doExecuteRequest(HttpUriRequest request) {
        log.info("开始发送请求----> method={}, url={}", request.getMethod(), request.getURI());
        HttpResponse response;
        try {
            HttpClient client = getClient();
            response = client.execute(request);
            HttpEntity entity = response.getEntity();
            //            log.info("收到请求返回===> response={}", res);
            return EntityUtils.toString(entity, StandardCharsets.UTF_8);
        } catch (IOException e) {
            e.printStackTrace();
            log.error("请求转发失败---->{},{}", request.getMethod(), request.getURI());
            return "";
        }
    }

    public HttpClientUtil headers(Map<String, String> headers) {
        this.headers.putAll(headers);
        return this;
    }

    public HttpClientUtil header(String name, String value) {
        this.headers.put(name, value);
        return this;
    }

    public HttpClientUtil params(Map<String, String> params) {
        this.params.putAll(params);
        return this;
    }

    public HttpClientUtil param(String key, String value) {
        this.params.put(key, value);
        return this;
    }

    public HttpClientUtil body(Object object) {
        return body(object, ContentType.APPLICATION_JSON);
    }

    /**
     * @param object      对象
     * @param contentType application/x-www-form-urlencoded，application/json，multipart/form-data，text/xml
     * @return HttpUtils
     * @see ContentType org.apache.http.entity.ContentType
     */
    public HttpClientUtil body(Object object, ContentType contentType) {
        this.bodyStr = JsonUtil.obj2String(object);
        this.contentType = contentType;
        return this;
    }

    public HttpClientUtil form(String paramKey, Object value) {
        this.forms.put(paramKey, value);
        return this;
    }

    public HttpClientUtil before(RequestListener listener) {
        this.listener = listener;
        return this;
    }

    public String execute() {
        HttpUriRequest request = buildHttpRequest();
        if (this.listener != null) {
            listener.beforeRequest(request);
        }
        return doExecuteRequest(request);
    }

    public <T> T execute(Class<T> clazz) {
        return JsonUtil.string2Obj(execute(), clazz);
    }

    public <T> T execute(TypeReference<T> valueTypeRef) {
        return JsonUtil.string2Obj(execute(), valueTypeRef);
    }

    private HttpUriRequest buildHttpRequest() {
        HttpUriRequest httpRequest = null;
        if (this.params != null && !this.params.isEmpty()) {
            for (Map.Entry<String, String> entry : this.params.entrySet()) {
                this.uriBuilder.addParameter(entry.getKey(), entry.getValue());
            }
        }
        try {
            URI uri = this.uriBuilder.build();
            httpRequest = HttpUriRequestFactory.createHttpRequest(this.method, uri);
            if (this.headers != null && !this.headers.isEmpty()) {
                for (Map.Entry<String, String> entry : this.headers.entrySet()) {
                    httpRequest.addHeader(entry.getKey(), entry.getValue());
                }
            }
            if (this.bodyStr != null && isOneOf(RFC2616_ENTITY_ENC_METHODS, method.toString())) {
                StringEntity bodyEntity = new StringEntity(this.bodyStr, this.contentType);
                ((HttpEntityEnclosingRequestBase) httpRequest).setEntity(bodyEntity);
            }
            if (this.forms.size() > 0) {
                MultipartEntityBuilder builder = MultipartEntityBuilder.create();
                builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
                for (Map.Entry<String, Object> entry : this.forms.entrySet()) {
                    String paramKey = entry.getKey();
                    Object value = entry.getValue();
                    if (value instanceof File) {
                        builder.addBinaryBody(paramKey, (File) value);
                    } else if (value instanceof MultipartFile) {
                        MultipartFile file = (MultipartFile) value;
                        InputStream inputStream = file.getInputStream();
                        String originalFilename = file.getOriginalFilename();
                        builder.addBinaryBody(paramKey, inputStream, ContentType.DEFAULT_BINARY, originalFilename);
                    } else if (value instanceof String) {
                        builder.addTextBody(paramKey, (String) value);
                    }
                }
                ((HttpEntityEnclosingRequestBase) httpRequest).setEntity(builder.build());
            }
        } catch (URISyntaxException | IOException e) {
            e.printStackTrace();
        }
        return httpRequest;
    }

    private static final String[] RFC2616_ENTITY_ENC_METHODS = {
            "POST",
            "PUT"
    };

    private static boolean isOneOf(final String[] methods, final String method) {
        for (final String method2 : methods) {
            if (method2.equalsIgnoreCase(method)) {
                return true;
            }
        }
        return false;
    }

    public static HttpClientUtil get(String url, Object... urlVar) {
        return request(Method.GET, url, urlVar);
    }

    public static HttpClientUtil post(String url, Object... urlVar) {
        return request(Method.POST, url, urlVar);
    }

    public static HttpClientUtil head(String url, Object... urlVar) {
        return request(Method.HEAD, url, urlVar);
    }

    public static HttpClientUtil option(String url, Object... urlVar) {
        return request(Method.OPTIONS, url, urlVar);
    }

    public static HttpClientUtil put(String url, Object... urlVar) {
        return request(Method.PUT, url, urlVar);
    }

    public static HttpClientUtil delete(String url, Object... urlVar) {
        return request(Method.DELETE, url, urlVar);
    }

    public static HttpClientUtil trace(String url, Object... urlVar) {
        return request(Method.TRACE, url, urlVar);
    }

    public static HttpClientUtil patch(String url, Object... urlVar) {
        return request(Method.PATCH, url, urlVar);
    }

    private static HttpClientUtil request(Method method, String uriTemplate, Object... urlVar) {
        if (uriTemplate == null) {
            throw new IllegalArgumentException("url must not null");
        }
        HttpClientUtil httpClientUtil = new HttpClientUtil();
        httpClientUtil.method = method;
        httpClientUtil.uriBuilder = new URIBuilder(
                UriComponentsBuilder.fromUriString(uriTemplate).build(urlVar));
        return httpClientUtil;
    }

    public enum Method {
        GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE, PATCH
    }


    public interface RequestListener {
        void beforeRequest(HttpRequest request);
    }

    public static class HttpUriRequestFactory {
        private static HttpUriRequest createHttpRequest(Method method, URI uri) {
            switch (method) {
                case GET:
                    return new HttpGet(uri);
                case POST:
                    return new HttpPost(uri);
                case HEAD:
                    return new HttpHead(uri);
                case OPTIONS:
                    return new HttpOptions(uri);
                case PUT:
                    return new HttpPut(uri);
                case DELETE:
                    return new HttpDelete(uri);
                case TRACE:
                    return new HttpTrace(uri);
                default:
                    return new HttpPatch(uri);
            }
//            return new HttpRequestAdapter(uri, method);
        }
    }

    public static class HttpRequestAdapter extends HttpRequestBase {
        private String methodName;

        public HttpRequestAdapter(URI uri, Method method) {
            this.setURI(uri);
            this.methodName = method.toString();
        }

        @Override
        public String getMethod() {
            return methodName;
        }
    }

    public static void main(String[] args) {
        System.out.println(HttpClientUtil.get("http://www.baidu.com")
                .execute());
    }
}

